﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xunit;
using YAPL.Language.SourceParser;
using YAPL.Language.Ast;
using YAPL.Language.SourceParser.XML;
using System.IO;

namespace YAPL.Test.YAPL.Language.Parser.XML {
	public class XmlInputParserTest {

		[Fact]
		public void EmptyProgramShouldBeParsedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.EmptyProgram_xml);
			Assert.IsType(typeof(ProgramDefExpression), expr);
			var programDef = (ProgramDefExpression)expr;

			var body = programDef.ProgramBody;
			
			//ensure body has no expressions
			Assert.Empty(body.Expressions);
		}

		[Fact]
		public void FunctionCallNoParamsProgramShouldBeParsedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.FunctionCallNoParams_xml);
			Assert.IsType(typeof(ProgramDefExpression), expr);
			var programDef = (ProgramDefExpression) expr;

			//ensure program body has 2 expressions
			var bodyExprs = programDef.ProgramBody.Expressions;
			Assert.Equal(2, bodyExprs.Count);

			//ensure function has proper name and params
			var helloWorldFunc = (FunctionDefExpression)bodyExprs[0];
			Assert.Equal("ret_string", helloWorldFunc.Name);
			Assert.Empty(helloWorldFunc.Parameters);

			//Check function body
			Assert.Equal(1,helloWorldFunc.FunctionBody.Expressions.Count);
			var funcFirstExpr = helloWorldFunc.FunctionBody.Expressions[0];
			Assert.IsType(typeof(ReturnExpression), funcFirstExpr);
			var returnExpr = (ReturnExpression) funcFirstExpr;

			//Check return statement
			Assert.IsType(typeof(ConstantExpression), returnExpr.Statement);
			var retStmt = (ConstantExpression) returnExpr.Statement;
			Assert.Equal("string returned from function", retStmt.Value);



			//ensure function call expression is parsed properly
			var firstExpr = bodyExprs[1];
			Assert.IsType(typeof(FunctionCallExpression), firstExpr);
			var functionCall = (FunctionCallExpression) firstExpr;
			Assert.Equal("ret_string", functionCall.MethodName);
			Assert.Empty(functionCall.Arguments);
		}

		[Fact]
		public void HelloWorldProgramShouldBeParsedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.HelloWorld_xml);
			Assert.IsType(typeof (ProgramDefExpression), expr);
			var programDef = (ProgramDefExpression) expr;



			//ensure program body has one expression
			var bodyExprs = programDef.ProgramBody.Expressions;
			Assert.Equal(1, bodyExprs.Count);

			//ensure function call expression is parsed properly
			var firstExpr = bodyExprs[0];
			Assert.IsType(typeof (FunctionCallExpression), firstExpr);
			var functionCall = (FunctionCallExpression) firstExpr;
			Assert.Equal("print",functionCall.MethodName);

			//ensure function is called with 1 argument
			Assert.Equal(1,functionCall.Arguments.Count);
			var arg = functionCall.Arguments[0];
			Assert.IsType(typeof (ConstantExpression), arg);
			var constant = (ConstantExpression) arg;
			Assert.Equal("Hello, World! Ha-ha-ha! It works!",constant.Value);
		}

		[Fact]
		public void AssignmentAndVariablesProgramShouldBeParsedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.AssignmentAndVariables_xml);
			Assert.IsType(typeof(ProgramDefExpression), expr);
			var programDef = (ProgramDefExpression)expr;


			var bodyExprs = programDef.ProgramBody.Expressions;

			Assert.Equal(6,bodyExprs.Count);

			Assert.IsType(typeof (AssignmentExpression), bodyExprs[0]);
			Assert.IsType(typeof(AssignmentExpression), bodyExprs[1]);
			Assert.IsType(typeof(AssignmentExpression), bodyExprs[2]);
			Assert.IsType(typeof(FunctionCallExpression), bodyExprs[3]);
			Assert.IsType(typeof(FunctionCallExpression), bodyExprs[4]);
			Assert.IsType(typeof(FunctionCallExpression), bodyExprs[5]);

			//verify first function call
			var funcCall1 = (FunctionCallExpression) bodyExprs[3];
			Assert.Equal("print",funcCall1.MethodName);
			Assert.Equal(1,funcCall1.Arguments.Count);
			var arg = funcCall1.Arguments[0];
			Assert.IsType(typeof (VariableExpression), arg);
			var variable = (VariableExpression) arg;
			Assert.Equal("a",variable.Name);

			//verify first assignment
			var assignment1 = (AssignmentExpression) bodyExprs[0];
			Assert.IsType(typeof(VariableExpression), assignment1.Left);
			Assert.IsType(typeof(ConstantExpression), assignment1.Right);
			var aVar = (VariableExpression) assignment1.Left;
			Assert.Equal("a", aVar.Name);
		}

		[Fact]
		public void SimpleMathProgramShouldBeParsedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.SimpleMath_xml);
			Assert.IsType(typeof(ProgramDefExpression), expr);
			var programDef = (ProgramDefExpression)expr;


			var bodyExprs = programDef.ProgramBody.Expressions;

			Assert.Equal(3, bodyExprs.Count);

			//Ensure Expressions have proper types
			Assert.IsType(typeof(AssignmentExpression), bodyExprs[0]);
			Assert.IsType(typeof(AssignmentExpression), bodyExprs[1]);
			Assert.IsType(typeof(FunctionCallExpression), bodyExprs[2]);

			//Verify math expressions
			var assignment2 = (AssignmentExpression)bodyExprs[1];
			Assert.IsType(typeof(VariableExpression), assignment2.Left);
			Assert.IsType(typeof(BinaryExpression), assignment2.Right);
			var bVar = (VariableExpression)assignment2.Left;
			Assert.Equal("b", bVar.Name);

			var binary1 = (BinaryExpression)assignment2.Right;
			Assert.Equal(BinaryOperator.Multiply, binary1.Operator);

			Assert.IsType(typeof(BinaryExpression), binary1.Right);

			var binary2 = (BinaryExpression)binary1.Right;
			Assert.Equal(BinaryOperator.Plus, binary2.Operator);
			Assert.IsType(typeof(VariableExpression), binary2.Left);
			Assert.IsType(typeof(ConstantExpression), binary2.Right);

			Assert.IsType(typeof(UnaryExpression), binary1.Left);

		}

		[Fact]
		public void SimpleTypesProgramShouldBeParsedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.SimpleTypes_xml);
			Assert.IsType(typeof(ProgramDefExpression), expr);
			var programDef = (ProgramDefExpression)expr;

			var bodyExprs = programDef.ProgramBody.Expressions;

			Assert.Equal(6, bodyExprs.Count);

			Assert.IsType(typeof(AssignmentExpression), bodyExprs[0]);
			Assert.IsType(typeof(AssignmentExpression), bodyExprs[1]);
			Assert.IsType(typeof(AssignmentExpression), bodyExprs[2]);
			Assert.IsType(typeof(FunctionCallExpression), bodyExprs[3]);
			Assert.IsType(typeof(FunctionCallExpression), bodyExprs[4]);
			Assert.IsType(typeof(FunctionCallExpression), bodyExprs[5]);
		}

		[Fact]
		public void UnaryOperatorsProgramShouldBeParsedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.UnaryOperators_xml);
			Assert.IsType(typeof(ProgramDefExpression), expr);
			var programDef = (ProgramDefExpression)expr;

			var bodyExprs = programDef.ProgramBody.Expressions;

			Assert.Equal(6, bodyExprs.Count);

			Assert.IsType(typeof(AssignmentExpression), bodyExprs[0]);
			Assert.IsType(typeof(UnaryExpression), bodyExprs[1]);
			Assert.IsType(typeof(UnaryExpression), bodyExprs[2]);
			Assert.IsType(typeof(UnaryExpression), bodyExprs[3]);
			Assert.IsType(typeof(AssignmentExpression), bodyExprs[4]);
			Assert.IsType(typeof(FunctionCallExpression), bodyExprs[5]);
		}

		[Fact]
		public void NestedFunctionCallProgramShouldBeParsedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.NestedFunctionCall_xml);
			Assert.IsType(typeof(ProgramDefExpression), expr);
			var programDef = (ProgramDefExpression)expr;


			var bodyExprs = programDef.ProgramBody.Expressions;
			Assert.Equal(3, bodyExprs.Count);
			Assert.IsType(typeof(FunctionCallExpression), bodyExprs[2]);
		}

		[Fact]
		public void PrintProgramShouldBeParsedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.Print_xml);
			Assert.IsType(typeof(ProgramDefExpression), expr);
			var programDef = (ProgramDefExpression)expr;

			var bodyExprs = programDef.ProgramBody.Expressions;
			Assert.Equal(1, bodyExprs.Count);
			Assert.IsType(typeof(FunctionCallExpression), bodyExprs[0]);

			var funcCall = (FunctionCallExpression) bodyExprs[0];
			Assert.Equal(5, funcCall.Arguments.Count);
		}

		[Fact]
		public void FunctionWithParamsProgramShouldBeParsedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.FunctionWithParams_xml);
			Assert.IsType(typeof(ProgramDefExpression), expr);
			var programDef = (ProgramDefExpression)expr;

			//verify program body
			var bodyExprs = programDef.ProgramBody.Expressions;
			Assert.Equal(2, bodyExprs.Count);
			Assert.IsType(typeof(FunctionCallExpression), bodyExprs[1]);

		}

		[Fact]
		public void CallingMethodOnSimpleTypeProgramShouldBeParsedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.CallingMethodOnSimpleType_xml);
			Assert.IsType(typeof(ProgramDefExpression), expr);
			var programDef = (ProgramDefExpression)expr;

			//verify program body
			var bodyExprs = programDef.ProgramBody.Expressions;
			Assert.Equal(2, bodyExprs.Count);

			Assert.IsType(typeof(FunctionCallExpression), bodyExprs[1]);
			var printFuncCall = (FunctionCallExpression) bodyExprs[1];

			//verify 'reverse' method call
			Assert.IsType(typeof(FunctionCallExpression), printFuncCall.Arguments[1]);
			var funcCall = (FunctionCallExpression)printFuncCall.Arguments[1];
			Assert.NotNull(funcCall.CallingObject);
			Assert.IsType(typeof (VariableExpression), funcCall.CallingObject);
			Assert.Equal("reverse",funcCall.MethodName);
		}

		[Fact]
		public void SimpleClassProgramShouldBeParsedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.SimpleClass_xml);
			Assert.IsType(typeof(ProgramDefExpression), expr);
			var programDef = (ProgramDefExpression)expr;

			Assert.Equal(3,programDef.ProgramBody.Expressions.Count);
			var class_def = (ClassDefExpression) programDef.ProgramBody.Expressions[0];
			Assert.Equal(1, class_def.ClassBody.Expressions.Count);
			var func = (FunctionDefExpression) class_def.ClassBody.Expressions[0];
			var assignment = (AssignmentExpression)func.FunctionBody.Expressions[0];
			Assert.IsType(typeof (InstanceVariableExpression), assignment.Left);
		}
	}
}